/**
 * ZHU framework. Copyright 2012-, all rights reserved.
 *
 * $Id: ReflectionUtility.java 21 2012-08-14 09:20:50Z zhuxiaopeng $
 * $Revision: 21 $
 * $Date: 2012-08-14 17:20:50 +0800 (二, 2012-08-14) $
 * $Author: zhuxiaopeng $
 */
package zhu.framework.util;

import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.logging.*;

/**
 * <p>
 * </p>
 * 
 * @author $Author: zhuxiaopeng $
 * @version $Revision: 21 $ - $Date: 2012-08-14 17:20:50 +0800 (二, 2012-08-14) $
 */
public final class ReflectionUtility {
	/**
	 * このClassのデフォルトの<code>Logger</code>取得する。
	 * 特に理由のない限り、このClass内ではこのメソッドを通じてLoggerを取得することを推奨する。
	 * 
	 * @return このClassのデフォルトの<code>Logger</code>
	 */
	static private Logger getLogger() {
		final LoggerManager manager = LoggerManager.getInstance();
		final Logger logger = manager.getLogger(ReflectionUtility.class);
		return logger;
	}

	private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class[] {};

	private static final Object[] EMPTY_PARAMS = new Object[] {};

	static public <T> T newInstance(final Class<T> clazz) {
		return newInstance(clazz, EMPTY_CLASS_ARRAY, false, EMPTY_PARAMS);
	}

	static public <T> T newInstance(final Class<T> clazz, final boolean overrideMemberAccessibility) {
		return newInstance(clazz, EMPTY_CLASS_ARRAY, overrideMemberAccessibility, EMPTY_PARAMS);
	}

	static public <T> T newInstance(final Class<T> clazz, final Class<?>[] signature, final Object... params) {
		return newInstance(clazz, signature, false, params);
	}

	static public <T> T newInstance(final Class<T> clazz, final Class<?>[] signature, final boolean overrideMemberAccessibility,
			final Object... params) {
		MyUtility.checkMethodParameterNotNull("clazz", clazz);
		MyUtility.checkMethodParameterNotNull("signature", signature);
		MyUtility.checkMethodParameterNotNull("params", params);
		final Logger logger = getLogger();
		//
		final T instance;
		try {
			// Logging only
			if (logger.isLoggable(Level.FINE)) {
				logger.fine("Instantiating object of class : " + clazz);
			}
			if (logger.isLoggable(Level.FINER)) {
				final StringBuilder buf = new StringBuilder(1024);
				buf.append("Using the constructor : sig=");
				final int c = signature.length;
				for (int i = 0; i < c; i++) {
					if (i != 0) {
						buf.append(", ");
					}
					buf.append(signature[i]);
				}
				logger.finer(buf.toString());
			}
			if (logger.isLoggable(Level.FINEST)) {
				final StringBuilder buf = new StringBuilder(1024);
				buf.append("With the parameters : ");
				final int c = params.length;
				for (int i = 0; i < c; i++) {
					if (i != 0) {
						buf.append(", ");
					}
					buf.append(params[i]);
				}
				logger.finest(buf.toString());
			}
			// True body
			final Constructor<T> constructor = clazz.getConstructor(signature);
			if (constructor.isAccessible() == false && overrideMemberAccessibility) {
				constructor.setAccessible(true);
				try {
					instance = constructor.newInstance(params);
				} finally {
					constructor.setAccessible(false);
				}
			} else {
				instance = constructor.newInstance(params);
			}
		} catch (final InvocationTargetException ex) {
			MyUtility.rethrowInvocationTargetException(ex);
			throw new RuntimeException("Cannot reach here");
		} catch (final IllegalAccessException ex) {
			logger.throwing("zhu.framework.util.ReflectionUtility", "newInstance", ex);
			throw new IllegalAccessError(ex.getMessage());
		} catch (final InstantiationException ex) {
			logger.throwing("zhu.framework.util.ReflectionUtility", "newInstance", ex);
			throw new IllegalAccessError(ex.getMessage());
		} catch (final NoSuchMethodException ex) {
			logger.throwing("zhu.framework.util.ReflectionUtility", "newInstance", ex);
			throw new IllegalAccessError(ex.getMessage());
		}
		return instance;
	}

	static public Object invoke(final Method method, final boolean overrideMemberAccessibility, final Object instance, final Object... args) {
		MyUtility.checkMethodParameterNotNull("method", method);
		//
		try {
			if (method.isAccessible() == false && overrideMemberAccessibility) {
				method.setAccessible(true);
				try {
					return method.invoke(instance, args);
				} finally {
					method.setAccessible(false);
				}
			} else {
				return method.invoke(instance, args);
			}
		} catch (final IllegalAccessException ex) {
			final IllegalAccessError e = new IllegalAccessError(ex.getMessage());
			final Logger logger = getLogger();
			logger.throwing("zhu.framework.util.ReflectionUtility", "invoke", e);
			throw e;
		} catch (final InvocationTargetException e) {
			MyUtility.rethrowInvocationTargetException(e);
			throw new RuntimeException("Cannot reach here");
		}
	}

	static public void inject(final Object target, final Object object, final Method injectionMethod, final boolean overrideMemberAccessibility) {
		MyUtility.checkMethodParameterNotNull("target", target);
		MyUtility.checkMethodParameterNotNull("injectionMethod", injectionMethod);
		//
		try {
			if (injectionMethod.isAccessible() == false && overrideMemberAccessibility) {
				injectionMethod.setAccessible(true);
				try {
					injectionMethod.invoke(target, object);
				} finally {
					injectionMethod.setAccessible(false);
				}
			} else {
				injectionMethod.invoke(target, object);
			}
		} catch (final IllegalAccessException ex) {
			final IllegalAccessError e = new IllegalAccessError(ex.getMessage());
			final Logger logger = getLogger();
			logger.throwing("zhu.framework.util.ReflectionUtility", "inject", e);
			throw e;
		} catch (final InvocationTargetException e) {
			MyUtility.rethrowInvocationTargetException(e);
			throw new RuntimeException("Cannot reach here");
		}
	}

	static public void inject(final Object target, final Object object, final Field injectionTarget, final boolean overrideMemberAccessibility) {
		MyUtility.checkMethodParameterNotNull("target", target);
		MyUtility.checkMethodParameterNotNull("injectionTarget", injectionTarget);
		//
		try {
			if (injectionTarget.isAccessible() == false && overrideMemberAccessibility) {
				injectionTarget.setAccessible(true);
				try {
					injectionTarget.set(target, object);
				} finally {
					injectionTarget.setAccessible(false);
				}
			} else {
				injectionTarget.set(target, object);
			}
		} catch (final IllegalAccessException ex) {
			final IllegalAccessError e = new IllegalAccessError(ex.getMessage());
			final Logger logger = getLogger();
			logger.throwing("zhu.framework.util.ReflectionUtility", "inject", e);
			throw e;
		}
	}

	static public Object getFieldValue(final Field field, final boolean overrideMemberAccessibility, final Object instance) {
		MyUtility.checkMethodParameterNotNull("field", field);
		MyUtility.checkMethodParameterNotNull("instance", instance);
		//
		final Object value;
		try {
			if (field.isAccessible() == false && overrideMemberAccessibility) {
				field.setAccessible(true);
				try {
					value = field.get(instance);
				} finally {
					field.setAccessible(false);
				}
			} else {
				value = field.get(instance);
			}
		} catch (final IllegalAccessException ex) {
			final IllegalAccessError e = new IllegalAccessError(ex.getMessage());
			final Logger logger = getLogger();
			logger.throwing("zhu.framework.util.ReflectionUtility", "getFieldValue", e);
			throw e;
		}
		return value;
	}

	static public List<Method> getAnnotatedMethods(final Class<?> clazz, final Class<? extends Annotation> annotation) {
		MyUtility.checkMethodParameterNotNull("clazz", clazz);
		MyUtility.checkMethodParameterNotNull("annotation", annotation);
		//
		final Logger logger = getLogger();
		if (logger.isLoggable(Level.FINE)) {
			logger.fine(String.format("Finding methods annotated by %s in %s", annotation, clazz));
		}
		//
		final List<Method> methodList = new ArrayList<Method>();
		doGetAnnotatedMethods(clazz, annotation, methodList);
		return methodList;
	}

	static private void doGetAnnotatedMethods(final Class<?> clazz, final Class<? extends Annotation> annotation, final List<Method> methodList) {
		assert (clazz != null);
		assert (annotation != null);
		assert (methodList != null);
		//
		final Logger logger = getLogger();
		if (logger.isLoggable(Level.FINER)) {
			logger.finer(String.format("Finding methods annotated by %s : current class being inspected is %s", annotation, clazz));
		}
		//
		for (final Method method : clazz.getDeclaredMethods()) {
			if (method.isAnnotationPresent(annotation)) {
				if (logger.isLoggable(Level.FINER)) {
					logger.finer(String.format("Found a method annotated by %s : %s#%s", annotation, clazz, method));
				}
				//
				methodList.add(method);
			}
		}
		final Class<?> superClass = clazz.getSuperclass();
		if (superClass != null && superClass.equals(Object.class) == false) {
			doGetAnnotatedMethods(superClass, annotation, methodList);
		}
	}

	static public List<Field> getAnnotatedFields(final Class<?> clazz, final Class<? extends Annotation> annotation) {
		MyUtility.checkMethodParameterNotNull("clazz", clazz);
		MyUtility.checkMethodParameterNotNull("annotation", annotation);
		//
		final Logger logger = getLogger();
		if (logger.isLoggable(Level.FINE)) {
			logger.fine(String.format("Finding fields annotated by %s in %s", annotation, clazz));
		}
		//
		final List<Field> fieldList = new ArrayList<Field>();
		doGetAnnotatedFields(clazz, annotation, fieldList);
		return fieldList;
	}

	static private void doGetAnnotatedFields(final Class<?> clazz, final Class<? extends Annotation> annotation, final List<Field> fieldList) {
		assert (clazz != null);
		assert (annotation != null);
		assert (fieldList != null);
		//
		final Logger logger = getLogger();
		if (logger.isLoggable(Level.FINER)) {
			logger.finer(String.format("Finding fields annotated by %s : current class being inspected is %s", annotation, clazz));
		}
		//
		for (final Field field : clazz.getDeclaredFields()) {
			if (field.isAnnotationPresent(annotation)) {
				if (logger.isLoggable(Level.FINER)) {
					logger.finer(String.format("Found a field annotated by %s : %s#%s", annotation, clazz, field));
				}
				//
				fieldList.add(field);
			}
		}
		final Class<?> superClass = clazz.getSuperclass();
		if (superClass != null && superClass.equals(Object.class) == false) {
			doGetAnnotatedFields(superClass, annotation, fieldList);
		}
	}

	static public Class<?> getTypeArgument(final Type type, final int argumentIndex) {
		MyUtility.checkMethodParameterNotNull("type", type);
		if (type instanceof ParameterizedType == false) {
			final IllegalArgumentException e = new IllegalArgumentException(String.format("Type is not a ParameterizedType : %s", type));
			final Logger logger = getLogger();
			logger.throwing("zhu.framework.util.ReflectionUtility", "getTypeArgument", e);
			throw e;
		}
		if (argumentIndex < 0) {
			final ArrayIndexOutOfBoundsException e = new ArrayIndexOutOfBoundsException(argumentIndex);
			final Logger logger = getLogger();
			logger.throwing("zhu.framework.util.ReflectionUtility", "getTypeArgument", e);
			throw e;
		}
		//
		final ParameterizedType paramType = (ParameterizedType) type;
		return (Class<?>) paramType.getActualTypeArguments()[argumentIndex];
	}

	/**
	 * This is<code>private</code> to prevent instantiation.
	 */
	private ReflectionUtility() {
	}
}
